#include "src/interface/image.h"

#include <QDebug>
#include <QFileInfo>

#include "src/defines/antialiasingType.h"
#include "src/defines/filterType.h"
#include "src/defines/light.h"
#include "src/defines/operation.h"
#include "src/defines/primitive.h"


#include "src/scene/pointLight.h"
#include "src/scene/areaLight.h"
#include "src/scene/combinedObject.h"
#include "src/scene/raytracing.h"
#include "src/scene/photonMapping.h"

#if USE_CONSOLE
Image::Image(Scene *_scene, QImage *_img) : image(_img), scene(_scene){
}

#else
Image::Image(QWidget *_parent, Scene *_scene, QImage *_img) : QWidget(_parent), image(_img), scene(_scene){
    zoom = 100.0;

    QSplitter *horizontalSplitter = new QSplitter(this);
    horizontalSplitter->setOrientation(Qt::Vertical);


    QPushButton *savePushButton = new QPushButton(tr("Salvar"));
    connect(savePushButton, SIGNAL(clicked()), this, SLOT(saveImage()));

	QLabel* zoomLabel = new QLabel(tr("Zoom:"));
	zoomDoubleSpinBox = new QDoubleSpinBox();
    zoomDoubleSpinBox->setSuffix("%");
    zoomDoubleSpinBox->setRange(0.01, 100000.0);
    zoomDoubleSpinBox->setSingleStep(25.0);
    zoomDoubleSpinBox->setValue(zoom);
    connect(zoomDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(changeZoom(double)));

    QHBoxLayout *zoomLayout = new QHBoxLayout;
    zoomLayout->addWidget(zoomLabel);
    zoomLayout->addWidget(zoomDoubleSpinBox);
    zoomLayout->addWidget(savePushButton);
	zoomLayout->addStretch();

    graphicView = new QGraphicsView(horizontalSplitter);
    graphicView->setDragMode(QGraphicsView::ScrollHandDrag);
    graphicView->setAlignment(Qt::AlignCenter);
    graphicView->setResizeAnchor(QGraphicsView::AnchorUnderMouse);

    QWidget *zoomWidget = new QWidget(horizontalSplitter);
    zoomWidget->setLayout(zoomLayout);
    zoomWidget->setMaximumHeight(70);

    horizontalSplitter->addWidget(graphicView);
    horizontalSplitter->addWidget(zoomWidget);


    resize(600, 500);

    QVBoxLayout *mainLayout = new QVBoxLayout;
    mainLayout->addWidget(horizontalSplitter);
//    mainLayout->addWidget(graphicView);
//    mainLayout->addLayout(zoomLayout);

    setLayout(mainLayout);
}
#endif //USE_CONSOLE

Image::~Image(){
    #if !USE_CONSOLE
    if (image) delete image;
    #endif //!USE_CONSOLE
}

#if !USE_CONSOLE
void Image::normalSize(){
    zoom = 100.0;
    zoomDoubleSpinBox->setValue(zoom);
}

void Image::zoomIn(){
    zoom += 25.0;
    zoomDoubleSpinBox->setValue(zoom);
}

void Image::zoomOut(){
    zoom -= 25.0;
    zoomDoubleSpinBox->setValue(zoom);
}

void Image::fitToWindow(){
    double imageAspectRatio = (double)image->width()/(double)image->height();
    double areaAspectRatio = (double)graphicView->width()/(double)graphicView->height();

    if (areaAspectRatio < imageAspectRatio){
        zoom = (double)graphicView->width()/(double)image->width();
    }else{
        zoom = (double)graphicView->height()/(double)image->height();
    }

    zoom -= 0.003;//error
    zoom *= 100;
    zoomDoubleSpinBox->setValue(zoom);
}

void Image::setAreaSize(int _w, int _h){
    graphicView->setFixedSize(_w, _h);
}

QImage *Image::getImage(){
    return image;
}

void Image::setImage(QImage *_img){
    delete image;
    image = _img;
}

void Image::changeZoom(double _z){
    zoom = _z;
    update();
}

void Image::update(QImage *_img){
    if (_img){
        delete image;
        image = _img;
    }

    int w = image->width()*zoom/100.0, h = image->height()*zoom/100.0;
    QImage printImage = image->scaled(w, h, Qt::KeepAspectRatio);

    delete graphicView->scene();

    QGraphicsScene *scene = new QGraphicsScene;
    scene->addPixmap(QPixmap::fromImage(printImage));
    graphicView->setScene(scene);
}
#endif //!USE_CONSOLE

void Image::save(QString pathName){
    QFileInfo pathNameFileInfo(pathName);
    if (pathNameFileInfo.suffix().isEmpty())
        pathName += ".png";

    image->save(pathName);

    //salvando a informação
    QString pathInfoName = pathNameFileInfo.path() + "/" + pathNameFileInfo.baseName() + ".info";

    QFile fileInfo(pathInfoName);
    fileInfo.open(QFile::WriteOnly);
	QTextStream stream(&fileInfo);

    stream << "Raytracing: " << endl;
    stream << "\tProfundidade: " << scene->getRaytracing()->getDepth() << endl;

    PhotonMapping *photonMapping = scene->getPhotonMapping();
    stream << "Photon Mapping: " << endl;
    stream << QString::fromUtf8("\tProfundidade dos fótons: ") << photonMapping->getPhotonDepth() << endl;
    stream << "\tMapa geral: " << endl;
    stream << QString::fromUtf8("\t\tNúmero de fótons emitidos: ") << photonMapping->getMaxPhotons() << endl;
    stream << QString::fromUtf8("\t\tNúmero de fótons mais próximos: ") << photonMapping->getMaxNearestPhotons() << endl;
    stream << "\t\tRaio máximo: " << photonMapping->getMaxRadius() << endl;
    stream << QString::fromUtf8("\tMapa de cáustica: ") << endl;
    stream << QString::fromUtf8("\t\tNúmero de fótons emitidos: ") << photonMapping->getCausticMaxPhotons() << endl;
    stream << QString::fromUtf8("\t\tNúmero de fótons mais próximos: ") << photonMapping->getCausticMaxNearestPhotons() << endl;
    stream << QString::fromUtf8("\t\tRaio máximo: ") << photonMapping->getCausticMaxRadius() << endl;

    stream << "\tFilto: ";
    switch(photonMapping->getFilter())
    {
        case WITHOUT_FILTER:
            stream << QString::fromUtf8("Não utiliza.") << endl;
            break;
        case CONE_FILTER:
            stream << "De cone com constante " << photonMapping->getConeFilterConstant() << "."<< endl;
            break;
        case GAUSSIAN_FILTER:
            stream << "Gaussiano." << endl;
            break;
    }

    stream << QString::fromUtf8("\tTeste de reflexão: ");
    switch(photonMapping->getTestReflexion())
    {
        case USE_K_VALUE:
            stream << "Valor dos K's." << endl;
            break;
        case USE_COLOR_VALUE:
            stream << "Valor da cor."<< endl;
            break;
        case USE_BOTH_VALUES:
            stream << "Mistura dos dois valores." << endl;
            break;
    }

    if(photonMapping->getKeepFirstBounce())
        stream << "\tPrimeira batida: Guarda." << endl;
    else
        stream << QString::fromUtf8("\tPrimeira batida: Não guarda.") << endl;

    unsigned int mapsSize = photonMapping->getMapsSize();
    stream << QString::fromUtf8("\tNúmero de mapas: ") << mapsSize << endl;
    for(unsigned int i = 0; i < mapsSize; i++)
    {
        stream << QString::fromUtf8("\t\tNúmero de photons no mapa ") << i << ": " << photonMapping->getMapISize(i) << endl;
    }




    Color backgroundColor = scene->getBackgroundColor();
    stream << "Cor de fundo: [" << backgroundColor.red << ", " << backgroundColor.green << ", " << backgroundColor.blue << "]"<< endl;

    const std::vector<Object *> objects = scene->getObjects();
    stream << "Objetos (" << objects.size() << "): " << endl;
    for(unsigned int i = 0; i < objects.size(); i++)
    {
        Object *object = objects[i];
        if(object->isPrimitive())
            writePrimitive(&stream, object, "\t");
        else
            writeCombinedObject(&stream, object, "\t");
    }

    const std::vector<Light *> lights = scene->getLights();
    stream << "Luzes (" << lights.size() << "): " << endl;
    for(unsigned int i = 0; i < lights.size(); i++)
    {
        writeLight(&stream, lights[i]);
    }

    stream << "Camera: " << endl;
    writeCamera(&stream, scene->getCamera());

    int days, hours, minutes, seconds;

    transformSecondToTime(&days, &hours, &minutes, &seconds, scene->getTreeTime());
    stream << "--Demorou " << days << " dias, " << hours << " horas, "
              << minutes << " minutos " << seconds << QString::fromUtf8(" segundos para fazer a árvore [") << scene->getTreeTime() << "]." << endl;

    transformSecondToTime(&days, &hours, &minutes, &seconds, scene->getRaytracingTime());
    stream << "--Demorou " << days << " dias, " << hours << " horas, "
              << minutes << " minutos " << seconds << " segundos para fazer o raytracing [" << scene->getRaytracingTime() << "]." << endl;

    transformSecondToTime(&days, &hours, &minutes, &seconds, scene->getEmitPhotonsTime());
    stream << "--Demorou " << days << " dias, " << hours << " horas, "
              << minutes << " minutos " << seconds << " segundos para emitir os photons [" << scene->getEmitPhotonsTime() << "]." << endl;

    transformSecondToTime(&days, &hours, &minutes, &seconds, scene->getPhotonMappingPhotonsOnlyTime());
    stream << "--Demorou " << days << " dias, " << hours << " horas, "
              << minutes << " minutos " << seconds << " segundos para rendeizar apenas os photons [" << scene->getPhotonMappingPhotonsOnlyTime() << "]." << endl;

    transformSecondToTime(&days, &hours, &minutes, &seconds, scene->getPhotonMappingTime());
    stream << "--Demorou " << days << " dias, " << hours << " horas, "
              << minutes << " minutos " << seconds << " segundos para fazer o photon mapping [" << scene->getPhotonMappingTime() << "]." << endl;


	fileInfo.close();
}

#if !USE_CONSOLE
void Image::saveImage(){
    QFileDialog dlg;
    dlg.setDefaultSuffix("png");
    QString pathName = dlg.getSaveFileName(this, tr("Escolha o nome do arquivo"), QDir::currentPath());

    if (pathName.isEmpty()) return;

    save(pathName);
}
#endif //!USE_CONSOLE

void Image::writeMaterial(QTextStream *_stream, Object *_obj, const QString &_ident){
    const Material &m = _obj->getMaterial();
    const Transformation &t = _obj->getTransformation();
    const Matrix &localToWorld = t.getLocalToWorld();
    const Matrix &worldToLocal = t.getWorldToLocal();


    QString texture = "";
    if(m.useTexture())
    {
        texture.append(_ident);
        texture.append("\tTextura\n");
        texture.append(_ident);
        texture.append("\t\tImagem: ");
        texture.append(m.texture->textureImagePathName);
        texture.append(";\n");
        texture.append(_ident);
        texture.append(QString::fromUtf8("\t\tRepetições: ["));
        texture.append(QString::number(m.texture->repeatTimesU));
        texture.append(" x ");
        texture.append(QString::number(m.texture->repeatTimesV));
        texture.append("];\n");
        texture.append(_ident);
        switch(m.texture->mappingType)
        {
            case ORIGINAL_TEXTURE:
                texture.append("\t\tMapeamento: Original;\n");
            break;
            case SPHERICAL_TEXTURE:
                texture.append(QString::fromUtf8("\t\tMapeamento: Esférico;\n"));
            break;
            case PLANAR_TEXTURE:
                texture.append("\t\tMapeamento: Planar;\n");
            break;
        }
        texture.append(_ident);
        switch(m.texture->coloringType)
        {
            case MULTIPLY:
                texture.append(QString::fromUtf8("\t\tColoração: Multiplicação;\n"));
            break;
            case REPLACE:
                texture.append(QString::fromUtf8("\t\tColoração: Subistituição;\n"));
            break;
        }
    }

    (*_stream) << _ident<< "\tAmbiente: [" << m.ambient.red << ", " << m.ambient.green << ", " << m.ambient.blue << "];" << endl
               << _ident<< "\tDifusa: [" << m.diffuse.red << ", " << m.diffuse.green << ", " << m.diffuse.blue << "];" << endl
               << _ident<< "\tEspecular: [" << m.specular.red << ", " << m.specular.green << ", " << m.specular.blue << "];" << endl
               << _ident<< "\tBrilho: " << m.specularPower << ";" << endl
               << _ident<< "\tKa: " << m.ka << ";" << endl
               << _ident<< "\tKd: " << m.kd << ";" << endl
               << _ident<< "\tKs: " << m.ks << ";" << endl
               << _ident<< "\tKt: " << m.kt << ";" << endl
               << _ident<< QString::fromUtf8("\tÍndice de refração: ") << m.refractiveIndex << ";" << endl
               << texture
               << _ident<< QString::fromUtf8("\tMatriz de transformação do sistema local para o mundo: ") << endl
               << _ident<< "\t\t| " << localToWorld[0][0] << " " << localToWorld[0][1] << " " << localToWorld[0][2] << " " << localToWorld[0][3] << " |"<< endl
               << _ident<< "\t\t| " << localToWorld[1][0] << " " << localToWorld[1][1] << " " << localToWorld[1][2] << " " << localToWorld[1][3] << " |"<< endl
               << _ident<< "\t\t| " << localToWorld[2][0] << " " << localToWorld[2][1] << " " << localToWorld[2][2] << " " << localToWorld[2][3] << " |"<< endl
               << _ident<< "\t\t| " << localToWorld[3][0] << " " << localToWorld[3][1] << " " << localToWorld[3][2] << " " << localToWorld[3][3] << " |"<< endl
               << _ident<< QString::fromUtf8("\tMatriz de transformação do sistema do mundo para o local: ") << endl
               << _ident<< "\t\t| " << worldToLocal[0][0] << " " << worldToLocal[0][1] << " " << worldToLocal[0][2] << " " << worldToLocal[0][3] << " |"<< endl
               << _ident<< "\t\t| " << worldToLocal[1][0] << " " << worldToLocal[1][1] << " " << worldToLocal[1][2] << " " << worldToLocal[1][3] << " |"<< endl
               << _ident<< "\t\t| " << worldToLocal[2][0] << " " << worldToLocal[2][1] << " " << worldToLocal[2][2] << " " << worldToLocal[2][3] << " |"<< endl
               << _ident<< "\t\t| " << worldToLocal[3][0] << " " << worldToLocal[3][1] << " " << worldToLocal[3][2] << " " << worldToLocal[3][3] << " |"<< endl;
}

void Image::writePrimitive(QTextStream *_stream, Object *_obj, const QString &_ident, bool _combined){
    switch(_obj->getType())
    {
        case CUBOID:
        {
            (*_stream) << _ident << "Cubo" << endl;
            break;
        }
        case SPHERE:
        {
            (*_stream) << _ident << "Esfera" << endl;
            break;
        }
        case CYLINDER:
        {
            (*_stream) << _ident << "Cilindro" << endl;
            break;
        }
        case CONE:
        {
            (*_stream) << _ident << "Cone" << endl;
            break;
        }
    }

    if(!_combined){
        writeMaterial(_stream, _obj, _ident);
   }
}

void Image::writeCombinedObject(QTextStream *_stream, Object *_obj, const QString &_ident, bool _combined){

    switch(_obj->getType())
    {
        case UNION:
        {
            (*_stream) << _ident << QString::fromUtf8("União") << endl;
            break;
        }
        case INTERSECTION:
        {
            (*_stream) << _ident << QString::fromUtf8("Interseção") << endl;
            break;
        }
        case DIFFERENCE:
        {
            (*_stream) << _ident << QString::fromUtf8("Diferença") << endl;
            break;
        }
    }

    const std::vector<Object *> objects = ((CombinedObject *)_obj)->getObjects();
    for(unsigned int i = 0; i < objects.size(); i++)
    {
        if(objects[i]->isPrimitive())
            writePrimitive(_stream, objects[i], _ident + "\t", true);
        else
            writeCombinedObject(_stream, objects[i], _ident + "\t", true);
    }


    if(!_combined){
        writeMaterial(_stream, _obj, _ident);
   }
}

void Image::writeLight(QTextStream *_stream, Light *_light){
    switch(_light->getType())
    {
        case AMBIENT_LIGHT:
        {
            Color color = _light->getColor();
            (*_stream) << "\tAmbiente" << endl
                       << "\t\tCor: [" << color.red << ", " << color.green << ", " << color.blue << "];"<< endl;
            break;
        }
        case POINT_LIGHT:
        {
            Color color = _light->getColor();
            Vector3D position = ((PointLight *)_light)->getPosition();
            (*_stream) << "\tPontual" << endl
                       << "\t\tCor: [" << color.red << ", " << color.green << ", " << color.blue << "];"<< endl
                       << "\t\tEnergia: " << _light->getEnergy() << ";"<< endl
                       << QString::fromUtf8("\t\tPosição: [") << position.x << ", " << position.y << ", " << position.z << "];"<< endl;
            break;
        }
        case AREA_LIGHT:
        {
            Color color = _light->getColor();
            Vector3D position = ((AreaLight *)_light)->getPosition();
            Vector3D a = ((AreaLight *)_light)->getA();
            Vector3D b = ((AreaLight *)_light)->getB();
            (*_stream) << "\tArea" << endl
                       << "\t\tCor: [" << color.red << ", " << color.green << ", " << color.blue << "];"<< endl
                       << "\t\tEnergia: " << _light->getEnergy() << ";"<< endl
                       << QString::fromUtf8("\t\tPosição: [") << position.x << ", " << position.y << ", " << position.z << "];"<< endl
                       << "\t\tA: [" << a.x << ", " << a.y << ", " << a.z << "];"<< endl
                       << "\t\tB: [" << b.x << ", " << b.y << ", " << b.z << "];"<< endl;
            break;
        }
    }
}

void Image::writeCamera(QTextStream *_stream, Camera *_camera){
    Vector3D eye = _camera->getEye();
    Vector3D lookAt = _camera->getLookAt();
    Vector3D up = _camera->getUp();
    (*_stream) << "\tEye: [" << eye.x << ", " << eye.y << ", " << eye.z << "];"<< endl
               << "\tLookAt: [" << lookAt.x << ", " << lookAt.y << ", " << lookAt.z << "];"<< endl
               << "\tUp: [" << up.x << ", " << up.y << ", " << up.z << "];"<< endl
               << "\tTamanho: " << _camera->getM() << " x " << _camera->getN() << ";"<< endl
               << "\tPlano da imagem: " << _camera->getImagePlane() << ";"<< endl
               << "\tZoom: " << _camera->getZoom() << ";"<< endl;

    bool b = false;
    switch(_camera->getAntialiasingType())
    {
        case REGULAR:
        {
            b = true;
            (*_stream) << "\tAntialiasing: Regular;"<< endl;
            break;
        }
        case JITTERING:
        {
            b = true;
            (*_stream) << "\tAntialiasing: Jittering;"<< endl;
            break;
        }
        case RANDOM:
        {
            b = true;
            (*_stream) << QString::fromUtf8("\tAntialiasing: Aleatório;")<< endl;
            break;
        }
    }

    if(b){
        (*_stream) << "\t\tMascara: " << _camera->getMPerPixel() << " x " << _camera->getNPerPixel() << ";"<< endl
                   << "\t\tFiltro: ";
        switch(_camera->getFilterType())
        {
            case MEAN:
            {
                (*_stream) << QString::fromUtf8("Média;")<< endl;
                break;
            }
            case GAUSSIAN:
            {
                (*_stream) << "Gaussiano:" << endl
                           << "\t\t\tSigma: " << _camera->getFilterGaussianSigma() << endl;
                break;
            }
            case LAPLACIAN:
            {
                (*_stream) << "Laplaciano:" << endl
                           << QString::fromUtf8("\t\t\tVizinhança: ");

                if(_camera->getFilterLaplacianNeighborhood() == LAPLACIAN_NEIGHBORHOOD_4)
                    (*_stream) << "4;" << endl << "\t\t\tCentro: ";
                else
                    (*_stream) << "8;" << endl << "\t\t\tCentro: ";

                if(_camera->getFilterLaplacianNegative() == LAPLACIAN_NEGATIVE)
                    (*_stream) << "negativo;" << endl;
                else
                    (*_stream) << "positivo;" << endl;
                break;
            }
            case WEIGHTED_AVERANGE:
            {
                (*_stream) << QString::fromUtf8("Média pondera;") << endl;
                break;
            }

        }
    }

    if(_camera->getDepthOfField())
    {
        (*_stream) << "\tDepth of Field:" << endl
                   << QString::fromUtf8("\t\tDistâcia focal: ") << _camera->getFocalDistance()<<endl
                   << "\t\tRaio: " << _camera->getRadius()<<endl;
    }
}


#if USE_CONSOLE
void Image::updateImageLine(unsigned int _i, uchar *_line){

    for(int i = 0; i < image->width(); i++){
        QRgb value = qRgb(_line[3*i], _line[(3*i)+1], _line[(3*i)+2]);
        image->setPixel(i, _i, value);
    }

//    update();
}
#endif //!USE_CONSOLE
